﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.IO;
using SampleGenerator.Models;
using System.Text;

namespace SampleGenerator.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult Generate()
        {
            Generate("Samples", new SamplesRegistry());
            Generate("Quickstart", new QuickstartRegistry());

            return View();
        }

        void Generate(string name, Registry registry)
        {
            //string rootPath = "c:\\test";
            string trunkPath = Path.GetDirectoryName(Path.GetDirectoryName(Server.MapPath("~/")));
            string deployRootPath = Path.Combine(trunkPath, "DeployRoot", name);
            string rootPath = Path.Combine(trunkPath, name);

            CleanRoot(rootPath);

            if (Directory.Exists(deployRootPath))
                CopyFolder(deployRootPath, rootPath, (x) => x.IndexOf(".svn") == -1);

            foreach (Project p in registry.Projects)
            {
                string projectPath = Path.Combine(rootPath, p.Name);

                RenderContext context = new RenderContext(projectPath, name);

                List<string> scaffoldFolders = new List<string>();

                scaffoldFolders.Add("~/Scaffold/" + name + "/Common");

                if (p.ViewEngine == ViewEngine.WebForms)
                {
                    scaffoldFolders.Add("~/Scaffold/" + name + "/WebForms");
                }
                else if (p.MvcVersion > MvcVersion.None)
                {
                    scaffoldFolders.Add("~/Scaffold/" + name + "/MvcCommon");
                    scaffoldFolders.Add("~/Scaffold/" + name + "/" + p.MvcVersion.ToString());
                }

                List<Sample> supportedSampleList = registry.Samples.Where(x => !(p.IsAspNetAjax && x.HasNoAspNetAjaxSupport)).ToList();

                CopyScaffold(scaffoldFolders, projectPath, p);

                TemplateScaffold(projectPath, p);

                RenderLayout(context, p, supportedSampleList);
                RenderWebConfig(context, p);

                foreach (Sample s in supportedSampleList)
                {
                    RenderSample(context, p, s, supportedSampleList.Count == 1);
                }

                RenderProject(context, p);
            }

            foreach (Solution s in registry.Solutions)
            {
                RenderSolution(rootPath, s);
            }
        }

        private void RenderSolution(string rootPath, Solution s)
        {
            using (StreamWriter w = System.IO.File.CreateText(Path.Combine(rootPath, s.Name + ".sln")))
            {
                w.WriteLine("Microsoft Visual Studio Solution File, Format Version " + (s.Version == VsVersion.VS2005 ? "9.00" : (s.Version == VsVersion.VS2008 ? "10.00" : "11.00")));
                w.WriteLine("# Visual Studio " + s.Version.ToString().Substring(2));

                foreach (Project p in s.Projects)
                    RenderSolutionProject(w, p, s, rootPath);

                w.WriteLine("Global");
                w.WriteLine("GlobalSection(SolutionConfigurationPlatforms) = preSolution");
                w.WriteLine("Debug|Any CPU = Debug|Any CPU");
                w.WriteLine("Release|.NET = Release|.NET");
                w.WriteLine("EndGlobalSection");
                w.WriteLine("GlobalSection(ProjectConfigurationPlatforms) = postSolution");

                foreach (Project p in s.Projects)
                    RenderSolutionProjectConfiguration(w, p);

                w.WriteLine("EndGlobalSection");
                w.WriteLine("GlobalSection(SolutionProperties) = preSolution");
                w.WriteLine("HideSolutionNode = FALSE");
                w.WriteLine("EndGlobalSection");
                w.WriteLine("EndGlobal");
            }
        }

        private void RenderSolutionProjectConfiguration(StreamWriter w, Project p)
        {
            w.WriteLine(p.ProjectGuid.ToString("B") + ".Debug|Any CPU.ActiveCfg = Debug|Any CPU");
            w.WriteLine(p.ProjectGuid.ToString("B") + ".Debug|Any CPU.Build.0 = Debug|Any CPU");
            w.WriteLine(p.ProjectGuid.ToString("B") + ".Release|Any CPU.ActiveCfg = Release|Any CPU");
            w.WriteLine(p.ProjectGuid.ToString("B") + ".Release|Any CPU.Build.0 = Release|Any CPU");
        }

        private void RenderSolutionProject(StreamWriter w, Project p, Solution s, string rootPath)
        {
            w.Write("Project(\"");
            w.Write(p.TypeGuid.ToString("B"));
            w.Write("\") = \"");
            w.Write(p.Name);
            w.Write("\", \"");
            w.Write(Path.Combine(p.Name, p.GetProjectFileName(s.Version)));
            w.Write("\", \"");
            w.Write(p.ProjectGuid.ToString("B"));
            w.WriteLine("\"");
            w.WriteLine("ProjectSection(WebsiteProperties) = preProject");
            w.WriteLine(@"Debug.AspNetCompiler.Debug = ""True""");
            w.WriteLine(@"Release.AspNetCompiler.Release = ""False""");
            w.WriteLine("EndProjectSection");
            w.WriteLine("EndProject");
        }

        private void TemplateScaffold(string projectPath, Project p)
        {
            if (Directory.Exists(projectPath))
            {
                Dictionary<string, string> templateArgs = new Dictionary<string, string>
                {
                    { "Namespace", p.Name },                    
                    { "Language", (p.Language == CodeLanguage.Cs ? "C#" : "VB") },                    
                    { "LanguageExtension", p.Language.ToString().ToLower() },
                    { "LineTerminator", (p.Language == CodeLanguage.Cs ? ";" : "") }
                };

                foreach (string file in Directory.EnumerateFiles(projectPath, "*.*", SearchOption.AllDirectories).Where(x => x.EndsWith("." + p.Language.ToString(), StringComparison.InvariantCultureIgnoreCase) || x.EndsWith(".aspx", StringComparison.InvariantCultureIgnoreCase) || x.EndsWith(".asax", StringComparison.InvariantCultureIgnoreCase) || x.EndsWith(".ashx", StringComparison.InvariantCultureIgnoreCase)))
                {
                    StringBuilder sb = new StringBuilder(System.IO.File.ReadAllText(file));

                    foreach (string key in templateArgs.Keys)
                        sb.Replace("$" + key, templateArgs[key]);

                    System.IO.File.WriteAllText(file, sb.ToString());
                }
            }
        }

        private void CopyScaffold(List<string> scaffoldFolders, string projectPath, Project p)
        {
            Func<string, bool> fileFilter;

            if (p.Language == CodeLanguage.Cs)
                fileFilter = (x) => x.IndexOf(".svn") == -1 && !x.EndsWith(".vb", StringComparison.InvariantCultureIgnoreCase);
            else
                fileFilter = (x) => x.IndexOf(".svn") == -1 && !x.EndsWith(".cs", StringComparison.InvariantCultureIgnoreCase);

            foreach (string path in scaffoldFolders)
            {
                string scaffoldSource = Server.MapPath(path);

                if (Directory.Exists(scaffoldSource))
                    CopyFolder(scaffoldSource, projectPath, fileFilter);
            }
        }

        private void CleanRoot(string rootPath)
        {
            try
            {
                if (Directory.Exists(rootPath))
                {
                    foreach (string file in Directory.EnumerateFiles(rootPath, "*.*", SearchOption.AllDirectories))
                        System.IO.File.Delete(file);

                    foreach (string folder in Directory.EnumerateDirectories(rootPath))
                        Directory.Delete(folder, true);
                }
                else
                {
                    Directory.CreateDirectory(rootPath);
                }
            }
            catch
            {
                // TODO: fix this
                CleanRoot(rootPath);
            }
        }

        private void RenderWebConfig(RenderContext context, Project p)
        {
            ViewDataDictionary viewData = new ViewDataDictionary();

            viewData["Project"] = p;

            string fileName = Path.Combine(context.Path, "Web.config");

            RenderFile(fileName, "~/views/" + context.Name + "/WebConfig.cshtml", viewData);
        }

        private void RenderProject(RenderContext context, Project p)
        {
            string rootPath = context.Path;

            if (!rootPath.EndsWith("\\"))
                rootPath += "\\";

            ViewDataDictionary viewData = new ViewDataDictionary();

            viewData["Project"] = p;

            IEnumerable<string> codeFiles = Directory.EnumerateFiles(rootPath, "*." + p.Language, SearchOption.AllDirectories);
            IEnumerable<string> contentFiles = Directory.EnumerateFiles(rootPath, "*.*", SearchOption.AllDirectories).Where(x => !x.EndsWith("proj", StringComparison.InvariantCultureIgnoreCase)).Except(codeFiles);

            viewData["CodeFiles"] = codeFiles.Select(x => x.Substring(rootPath.Length));
            viewData["ContentFiles"] = contentFiles.Select(x => x.Substring(rootPath.Length));

            for (int i = 0; i < p.Versions.Length; i++)
            {
                viewData["Version"] = p.Versions[i];

                RenderFile(Path.Combine(rootPath, p.GetProjectFileName(i)), "~/views/" + context.Name + "/Project.cshtml", viewData);
            }
        }

        private void RenderLayout(RenderContext context, Project p, List<Sample> samples)
        {
            string sourceTemplate = "~/views/" + context.Name + "/Layout.cshtml";

            if (System.IO.File.Exists(Server.MapPath(sourceTemplate)))
            {
                ViewDataDictionary viewData = new ViewDataDictionary();

                viewData["Project"] = p;
                viewData["Samples"] = samples;

                if (p.Language == CodeLanguage.Cs && p.ViewEngine == ViewEngine.WebForms)
                {
                    RenderFile(Path.Combine(context.Path, "Shared", "Site.master"), sourceTemplate, viewData);
                }
                else if (p.Language == CodeLanguage.Cs && p.ViewEngine == ViewEngine.Mvc)
                {
                    RenderFile(Path.Combine(context.Path, "Views\\Shared", "Site.master"), sourceTemplate, viewData);
                }
                else if (p.Language == CodeLanguage.Cs && p.ViewEngine == ViewEngine.Razor)
                {
                    RenderFile(Path.Combine(context.Path, "Views\\Shared", "_Layout.cshtml"), sourceTemplate, viewData);
                }
                else if (p.Language == CodeLanguage.Vb && p.ViewEngine == ViewEngine.WebForms)
                {
                    RenderFile(Path.Combine(context.Path, "Shared", "Site.master"), sourceTemplate, viewData);
                }
                else if (p.Language == CodeLanguage.Vb && p.ViewEngine == ViewEngine.Mvc)
                {
                    RenderFile(Path.Combine(context.Path, "Views\\Shared", "Site.master"), sourceTemplate, viewData);
                }
                else if (p.Language == CodeLanguage.Vb && p.ViewEngine == ViewEngine.Razor)
                {
                    RenderFile(Path.Combine(context.Path, "Views\\Shared", "_Layout.vbhtml"), sourceTemplate, viewData);
                }
            }
        }

        void RenderSample(RenderContext context, Project p, Sample s, bool isSingle)
        {
            ViewDataDictionary viewData = new ViewDataDictionary();

            viewData["Project"] = p;
            viewData["Sample"] = s;

            string sampleControllerPath = Path.Combine(context.Path, "Controllers");
            string sampleControllerName = (isSingle ? "Home" : s.Path) + "Controller";

            string sampleViewPath = context.Path;

            if (p.ViewEngine == ViewEngine.Mvc || p.ViewEngine == ViewEngine.Razor)
                sampleViewPath = Path.Combine(sampleViewPath, "Views");

            // TODO: handle single samples
            if (!isSingle)
            {
                sampleViewPath = Path.Combine(sampleViewPath, s.Path);
            }
            else if (p.ViewEngine == ViewEngine.Mvc || p.ViewEngine == ViewEngine.Razor)
            {
                sampleViewPath = Path.Combine(sampleViewPath, "Home");
            }

            if (p.Language == CodeLanguage.Cs && p.ViewEngine == ViewEngine.WebForms)
            {
                RenderFile(Path.Combine(sampleViewPath, "Default.aspx"), new string[] { "~/views/" + context.Name + "/" + s.Path + "/Index.cshtml", "~/views/" + context.Name + "/Generic/Index.cshtml" }, viewData);
                RenderFile(Path.Combine(sampleViewPath, "Default.aspx.cs"), new string[] { "~/views/" + context.Name + "/" + s.Path + "/Codebehind.cshtml", "~/views/" + context.Name + "/Generic/Codebehind.cshtml" }, viewData);
                RenderFile(Path.Combine(sampleViewPath, "Default.aspx.designer.cs"), new string[] { "~/views/" + context.Name + "/" + s.Path + "/Designer.cshtml", "~/views/" + context.Name + "/Generic/Designer.cshtml" }, viewData);
            }
            else if (p.Language == CodeLanguage.Cs && p.ViewEngine == ViewEngine.Mvc)
            {
                RenderFile(Path.Combine(sampleViewPath, "Index.aspx"), new string[] { "~/views/" + context.Name + "/" + s.Path + "/Index.cshtml", "~/views/" + context.Name + "/Generic/Index.cshtml" }, viewData);
                RenderFile(Path.Combine(sampleViewPath, "UploadResult.aspx"), new string[] { "~/views/" + context.Name + "/" + s.Path + "/UploadResult.cshtml", "~/views/" + context.Name + "/Generic/UploadResult.cshtml" }, viewData, false);
                RenderFile(Path.Combine(sampleControllerPath, sampleControllerName + ".cs"), new string[] { "~/views/" + context.Name + "/" + s.Path + "/Codebehind.cshtml", "~/views/" + context.Name + "/Generic/Codebehind.cshtml" }, viewData);
            }
            else if (p.Language == CodeLanguage.Cs && p.ViewEngine == ViewEngine.Razor)
            {
                RenderFile(Path.Combine(sampleViewPath, "Index.cshtml"), new string[] { "~/views/" + context.Name + "/" + s.Path + "/Index.cshtml", "~/views/" + context.Name + "/Generic/Index.cshtml" }, viewData);
                RenderFile(Path.Combine(sampleViewPath, "UploadResult.cshtml"), new string[] { "~/views/" + context.Name + "/" + s.Path + "/UploadResult.cshtml", "~/views/" + context.Name + "/Generic/UploadResult.cshtml" }, viewData, false);
                RenderFile(Path.Combine(sampleControllerPath, sampleControllerName + ".cs"), new string[] { "~/views/" + context.Name + "/" + s.Path + "/Codebehind.cshtml", "~/views/" + context.Name + "/Generic/Codebehind.cshtml" }, viewData);
            }
            else if (p.Language == CodeLanguage.Vb && p.ViewEngine == ViewEngine.WebForms)
            {
                RenderFile(Path.Combine(sampleViewPath, "Default.aspx"), new string[] { "~/views/" + context.Name + "/" + s.Path + "/Index.cshtml", "~/views/" + context.Name + "/Generic/Index.cshtml" }, viewData);
                RenderFile(Path.Combine(sampleViewPath, "Default.aspx.vb"), new string[] { "~/views/" + context.Name + "/" + s.Path + "/Codebehind.cshtml", "~/views/" + context.Name + "/Generic/Codebehind.cshtml" }, viewData);
                RenderFile(Path.Combine(sampleViewPath, "Default.aspx.designer.vb"), new string[] { "~/views/" + context.Name + "/" + s.Path + "/Designer.cshtml", "~/views/" + context.Name + "/Generic/Designer.cshtml" }, viewData);
            }
            else if (p.Language == CodeLanguage.Vb && p.ViewEngine == ViewEngine.Mvc)
            {
                RenderFile(Path.Combine(sampleViewPath, "Index.aspx"), new string[] { "~/views/" + context.Name + "/" + s.Path + "/Index.cshtml", "~/views/" + context.Name + "/Generic/Index.cshtml" }, viewData);
                RenderFile(Path.Combine(sampleViewPath, "UploadResult.aspx"), new string[] { "~/views/" + context.Name + "/" + s.Path + "/UploadResult.cshtml", "~/views/" + context.Name + "/Generic/UploadResult.cshtml" }, viewData, false);
                RenderFile(Path.Combine(sampleControllerPath, sampleControllerName + ".vb"), new string[] { "~/views/" + context.Name + "/" + s.Path + "/Codebehind.cshtml", "~/views/" + context.Name + "/Generic/Codebehind.cshtml" }, viewData);
            }
            else if (p.Language == CodeLanguage.Vb && p.ViewEngine == ViewEngine.Razor)
            {
                RenderFile(Path.Combine(sampleViewPath, "Index.vbhtml"), new string[] { "~/views/" + context.Name + "/" + s.Path + "/Index.cshtml", "~/views/" + context.Name + "/Generic/Index.cshtml" }, viewData);
                RenderFile(Path.Combine(sampleViewPath, "UploadResult.vbhtml"), new string[] { "~/views/" + context.Name + "/" + s.Path + "/UploadResult.cshtml", "~/views/" + context.Name + "/Generic/UploadResult.cshtml" }, viewData, false);
                RenderFile(Path.Combine(sampleControllerPath, sampleControllerName + ".vb"), new string[] { "~/views/" + context.Name + "/" + s.Path + "/Codebehind.cshtml", "~/views/" + context.Name + "/Generic/Codebehind.cshtml" }, viewData);
            }
        }

        void RenderFile(string fileName, string viewPath, ViewDataDictionary viewData)
        {
            RenderFile(fileName, new string[] { viewPath }, viewData);
        }

        void RenderFile(string fileName, string[] viewPathList, ViewDataDictionary viewData, bool isRequired = true)
        {
            foreach (string viewPath in viewPathList)
            {
                if (System.IO.File.Exists(Server.MapPath(viewPath)))
                {
                    string data = RenderPartialViewToString(viewPath, viewData);

                    Directory.CreateDirectory(Path.GetDirectoryName(fileName));

                    System.IO.File.WriteAllText(fileName, data);

                    return;
                }
            }

            if (isRequired)
                throw new Exception("Couldn't find view.");
        }

        string RenderPartialViewToString(string viewName, ViewDataDictionary viewData)
        {
            if (string.IsNullOrEmpty(viewName))
                viewName = ControllerContext.RouteData.GetRequiredString("action");

            using (StringWriter sw = new StringWriter())
            {
                ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
                ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, viewData, TempData, sw);

                viewResult.View.Render(viewContext, sw);

                return sw.GetStringBuilder().ToString();
            }
        }

        private void CopyFolder(string folder, string destFolder, Func<string, bool> filter)
        {
            Directory.CreateDirectory(destFolder);

            // TODO: pass in a filter
            foreach (string file in Directory.EnumerateFiles(folder, "*.*", SearchOption.AllDirectories).Where(x => filter(x)))
            {
                string fileName = Path.GetFileName(file);
                string filePath = Path.GetDirectoryName(file.Substring(folder.Length + (folder.EndsWith("\\") ? 0 : 1)));
                string destFilePath;

                if (!string.IsNullOrEmpty(filePath))
                {
                    string destFolderPath = Path.Combine(destFolder, filePath);

                    Directory.CreateDirectory(destFolderPath);

                    destFilePath = Path.Combine(destFolderPath, fileName);
                }
                else
                {
                    destFilePath = Path.Combine(destFolder, fileName);
                }

                System.IO.File.Copy(file, destFilePath);
            }
        }
    }
}
